<?php
namespace Pinenut;

use Pinenut\Model2\Embedded\Assoc;
use Pinenut\Files\File;

/**
 * Хранилище файлов и сопутствующих данных
 *
 *
 * @author kosh
 *        
 */
abstract class Files extends \Model {

	protected $_thumbsConfigClass; // = '\Files\ThumbsConfig';
	
	/**
	 *
	 * @var \Pinenut\Files\File
	 */
	public $file;

	/**
	 *
	 * @var \Pinenut\Files\File[]
	 */
	public $thumbs;

	// для блюра сохраняю размеры после ресайза
	protected $_resizedX, $_resizedY;

	static function fields() {
		return array_merge(parent::fields(), 
			[
				'file' => File::className(),
				'thumbs' => [
					Assoc::className(),
					File::className()
				]
			]);
	}

	static function create($filename, $params = []) {
		// создаем
		$file = new static();
		$file->populate($params, false);
		
		// сохраняем файл в систему хранения
		$file->file->putFile($filename);
		
		// генерим ноготки если надо
		
		// делаем еще что-нить
		
		// сейвим всё
		$file->save();
		
		return $file;
	}

	function getThumb($name) {
		// проверяем по конфе - доступен ли такой формат
		$configClass = $this->_thumbsConfigClass;
		$config = $configClass::get($name);
		
		if (empty($config)) {
			throw new \Exception('Wrong thumbnail', 404);
		}
		
		// включить обратно!
		if (! isset($this->thumbs[$name])) {
			// ногтя не существует - отдаём на генерацию
			$this->renderThumb($name, $config);
		}
		
		return $this->thumbs[$name];
	}

	function renderThumb($name, array $config) {
		// если бмп грузим одиим способом, иначе как обычно
		$image = $this->loadImage();
		
		$image = $this->resizeImage($image, $config);
		
		$image = $this->blurImage($image, $config);

		$image = $this->watermarkImage($image, $config);
		
		$filename = $this->compressImage($image, $config);
		
		$this->thumbs[$name]->putFile($filename);
		
		$this->save();
	}

	function loadImage( ) {
		if (preg_match('/^image\/.*bmp$/i', $this->file->mimeType)) {
			$image = static::imagecreatefrombmp($this->file->getBytes());
		} else {
			$image = imagecreatefromstring($this->file->getBytes());
		}
		
		return $image;
	}

	function resizeImage($image, $config) {
		$targetWidth = $config['width'];
		$targetHeight = $config['height'];
		$resize = $config['resize'];
		
		// ресайзим в зависимости от типа
		switch ($resize) {
			
			case 'crop': // вписываем в область, обрезая не поместившееся
				
				$width = imagesx($image);
				$height = imagesy($image);
				// echo $width . 'x' . $height . '<br>';
				
				// if ($this->getOwner()->cropCoords && (
				// preg_match('/([0-9\.]+),([0-9\.]+);([0-9\.]+),([0-9\.]+)/', $this->getOwner()->cropCoords, $m))
				// && ($m[3] - $m[1] > 0 && $m[4] - $m[2] > 0)) {
				// $xCorrection = round($width * $m[1]);
				// $yCorrection = round($height * $m[2]);
				
				// $width = round($width * ($m[3] - $m[1]));
				// $height = round($height * ($m[4] - $m[2]));
				
				// // $targetWidth = $width;
				// // $targetHeight = $height;
				// } else {
				$xCorrection = $yCorrection = 0;
				// }
				
				$ratio = $width / $height;
				if ($targetWidth / $targetHeight > $ratio) {
					$newHeight = $targetWidth / $ratio;
					$newWidth = $targetWidth;
				} else {
					$newWidth = $targetHeight * $ratio;
					$newHeight = $targetHeight;
				}
				
				$horizontalMiddle = $newWidth / 2; // + $xCorrection;
				$verticalMiddle = $newHeight / 2; // + $yCorrection;
				                                  
				// масштабируем
				$process = imagecreatetruecolor(round($newWidth), 
					round($newHeight));
				ImageAlphaBlending($process, true);
				
				// Если мы не трогали переменную fill -
				// пустота на картинке будет заполняться указанным цветом
				// Внесённые дополнения для спрайтогенерации
				
				// if($fill) imagefill($process, 0, 0, 0xffffff);
				
				imagecopyresampled($process, $image, 0, 0, $xCorrection, 
					$yCorrection, $newWidth, $newHeight, $width, $height);
				
				// кроп
				$image = imagecreatetruecolor($targetWidth, $targetHeight);
				// ImageAlphaBlending($image, true);
				
				// imagefill($idest, 0, 0, 0xffffff);
				imagecopyresampled($image, $process, 0, 0, 
					($horizontalMiddle - ($targetWidth / 2)), 
					($verticalMiddle - ($targetHeight / 2)), $targetWidth, 
					$targetHeight, $targetWidth, $targetHeight);
				
				$this->_resizedX = $targetWidth;
				$this->_resizedY = $targetHeight;
				
				imagedestroy($process);
				break;
			
			default:
			case 'fit': // вписываем в область, сохраняя пропорции сторон. Но большая сторона не может превышать заданные размеры
			case 'fat': // вписываем в область по меньшей стороне. эта шляпатень нужна для галерейки, когда картинки движутся во всякие стороны
			case 'fit-if-needed': // вписываем в облать, если не влазит, и не увеличиваем, если картинка меньше ограничений
				$width = imagesx($image);
				$height = imagesy($image);
				
				$horizontalRatio = $targetWidth / $width;
				$verticalRatio = $targetHeight / $height;
				
				$ratio = ('fat' == $resize) ? max($horizontalRatio, 
					$verticalRatio) : min($horizontalRatio, $verticalRatio);
				$useHorizontalRatio = ($horizontalRatio == $ratio);
				
				$newWidth = $useHorizontalRatio ? $targetWidth : floor(
					$width * $ratio);
				$newHeight = ! $useHorizontalRatio ? $targetHeight : floor(
					$height * $ratio);
				
				if ('fit-if-needed' == $resize) {
					if (($newWidth > $width) || ($newHeight > $height)) {
						$newWidth = $width;
						$newHeight = $height;
					}
				}
				
				$processImage = imagecreatetruecolor($newWidth, $newHeight);
				ImageAlphaBlending($processImage, true);
				
				// Если мы не трогали переменную fill -
				// пустота на картинке будет заполняться указанным цветом
				// Внесённые дополнения для спрайтогенерации
				// if($fill)imagefill($idest, 0, 0, 0xffffff);
				
				imagecopyresampled($processImage, $image, 0, 0, 0, 0, $newWidth, 
					$newHeight, $width, $height);
				
				$this->_resizedX = $newWidth;
				$this->_resizedY = $newHeight;
				
				$image = $processImage;
				break;
		}
		
		return $image;
	}

	/**
	 * Делаем быстрый блюр, если требуется
	 *
	 * @param unknown $image        	
	 * @param unknown $config        	
	 * @return resource unknown
	 */
	function blurImage($image, $config) {
		if (! isset($config['blur'])) {
			return $image;
		}
		
		// уменьшает исходное изображение на требуемую глубину блюра
		
		$c = intval($config['blur']);
		$x = $this->_resizedX / pow(2, $c);
		$y = $this->_resizedY / pow(2, $c);
		
		$tmp = imagecreatetruecolor($x, $y);
		
		imagecopyresampled($tmp, $image, 0, 0, 0, 0, $x, $y, $this->_resizedX, 
			$this->_resizedY);
		
		// постепенно восстанавливаем размер изображения постоянно блюря его
		
		for (; $c >= 0; $c --) {
			$x1 = $x;
			$y1 = $y;
			$x = $this->_resizedX / pow(2, $c);
			$y = $this->_resizedY / pow(2, $c);
			$tmp2 = imagecreatetruecolor($x, $y);
			imagecopyresampled($tmp2, $tmp, 0, 0, 0, 0, $x, $y, $x1, $y1);
			imagefilter($tmp2, IMG_FILTER_GAUSSIAN_BLUR);
			$tmp = $tmp2;
		}
		
		return $tmp;
	}
	
	
	/**
	 * Накладываем водяные знаки 
	 *
	 * @param unknown $image
	 * @param unknown $config
	 * @return resource unknown
	 */
	function watermarkImage($image, $config) {
		if (! isset($config['watermark'])) {
			return $image;
		}
		
		$watermarkFile = \Application::applicationPath() . $config['watermark']['image'];
		
		if (false !== $watermarkImage = imagecreatefrompng($watermarkFile)) {
			
			
			$position = isset($config['watermark']['position'])?$config['watermark']['position']:'fixed';
			
			$horizontal = isset($config['watermark']['horizontal'])?$config['watermark']['horizontal']:'left';
			$vertical = isset($config['watermark']['vertical'])?$config['watermark']['vertical']:'bottom';
			
			$place = isset($config['watermark']['place'])?$config['watermark']['place']:'bottom';
			
			$margin = isset($config['watermark']['margin'])?$config['watermark']['margin']:20;
			$marginRandom = isset($config['watermark']['marginRandom'])?$config['watermark']['marginRandom']:5;
			$rotate = isset($config['watermark']['rotate'])?$config['watermark']['rotate']:0;
			$rotateRandom = isset($config['watermark']['rotateRandom'])?$config['watermark']['rotateRandom']:10;

			$opacity = isset($config['watermark']['opacity'])?$config['watermark']['opacity']:10;
				
			
			// размеры картинки
			$targetWidth = imagesx($image);
			$targetHeight = imagesy($image);
			
			
			$marginX = 0;
			$marginY = 0;
			
			// определяем как позиционируем
			if ('longest' == $position) {
				// по длинной грани
				if ($targetHeight > $targetWidth) {
					// если нужно крутим, опредялем грань

					$place = $horizontal;
				} else {
					$place = $vertical;
				}
			} elseif ('shortest' == $position) {
				// по короткой грани
				if ($targetHeight < $targetWidth) {
					// если нужно крутим, опредялем грань
					$place = $horizontal;
				} else {
					$place = $vertical;
				}
			} else {
				// где-то...
			}
			
			// получаем размер водяного знака
			$watermarkWidth = imagesx($watermarkImage);
			$watermarkHeight = imagesy($watermarkImage);
			

			$repeat = ceil(max($targetWidth, $targetHeight) / $watermarkWidth) + 1;
			
			$processWidth = $watermarkWidth * $repeat;
			$processHeight = $watermarkHeight;
				
			
			// создаём маску
			$processImage = imagecreatetruecolor($processWidth, $processHeight);
			ImageAlphaBlending($processImage, true);
			
			// закрашиваю всю картинку прозрачным!
			$transparent = imagecolorallocatealpha( $processImage, 0, 0, 0, 127 );
			imagefill( $processImage, 0, 0, $transparent );
			imagecolortransparent($processImage, $transparent); // !!!!!!!!!! вот эта хуйня тут самая главная!!

			// самое время наложить надпись
			$watermarkImage = $this->textImage($watermarkImage, $config['watermark']);
			
			// добавляем на неё водяные знаки
			for ($i = 0; $i < $repeat; $i++) {
				imagecopy($processImage, $watermarkImage,
					$i * $watermarkWidth, 0,
					0, 0, 
					$watermarkWidth, $watermarkHeight);
			}
				

			// крутим
			
			if ('left' == $place || 'right' == $place) {
				$rotate +=  90;
			}
				
			$rotate += rand(-$rotateRandom, $rotateRandom);
				
			// крутим
			$processImage = imagerotate($processImage, floatval($rotate), $transparent);
			imagecolortransparent($processImage, $transparent); // !!!!!!!!!! вот эта хуйня тут самая главная!!

			
			// получаем размер водяного знака с учетом поворота
			$processkWidth = imagesx($processImage);
			$processHeight = imagesy($processImage);
			
			// считаем отступы
			switch ($place) {
				case 'left':
					$marginX = $margin + rand(- $marginRandom, $marginRandom);
					$marginY = - ($watermarkWidth / 3)*2;
					break;
				case 'right':
					$marginX = $targetWidth - $margin - $processkWidth + rand(- $marginRandom, $marginRandom);
					$marginY = - ($watermarkWidth / 3)*2;
					break;
				case 'top':
					$marginX = - ($watermarkWidth / 3)*2;
					$marginY = $margin + rand(- $marginRandom, $marginRandom);
					break;
				case 'bottom':
					$marginX = - ($watermarkWidth / 3)*2;
					$marginY = $targetHeight - $margin - $processHeight + rand(- $marginRandom, $marginRandom);
					break;
			}
			
			// со смещением накладываем на исходное изображение
			
			imagecopymerge($image, $processImage, $marginX, $marginY,
				0 , 
				0 ,
				imagesx($processImage), imagesy($processImage), $opacity);
			
		}
		
		return $image;
		
	}

	function compressImage($image, $config) {
		$targetType = @$config['type'];
		$targetQuality = @$config['quality'];
		
		// сохраняем
		if (null === $targetType)
			list (, $targetType) = explode('/', $this->file->mimeType);
			
			// сохраняем через жооопу - т.е. через файлы... сначала в файл генерим картинку,
			// потом её из того же места скармливаем gridfs
		$filename = tempnam('/tmp', 'thumbnail_');
		
		switch ($targetType) {
			case 'jpeg':
				if (null === $targetQuality)
					imagejpeg($image, $filename);
				else
					imagejpeg($image, $filename, $targetQuality);
				break;
			case 'png':
				if (null === $targetQuality)
					imagepng($image, $filename);
				else
					imagepng($image, $filename, 
						$targetQuality > 9 ? 9 : $targetQuality);
				break;
			
			case 'gif':
				if (null === $targetQuality)
					imagegif($image, $filename);
				else
					imagegif($image, $filename, $targetQuality);
				break;
			default:
				break;
		}
		
		return $filename;
	}
	
	
	function textImage($image, $config) {

		$align = isset($config['textAlign'])?$config['textAlign']:'left';
		$size = isset($config['textSize'])?$config['textSize']:12;
		$x = isset($config['textX'])?$config['textX']:0;
		$y = isset($config['textY'])?$config['textY']:0;
		$color = isset($config['textColor'])&& is_array($config['textColor'])
			? imagecolorallocate($image, $config['textColor'][0], $config['textColor'][1], $config['textColor'][2])
			: imagecolorallocate($image, 255,255,255);
		
		$fontfile = \Application::applicationPath() . $config['textFont'];
		$text = $config['textMethod'];
		// если есть метод - добываем строку из него, если нету - используем строку как текст
		if (method_exists($this, $text)) {
			$text = $this->$text();
		}
		
		$bounds = imagettfbbox ( $size , 0, $fontfile , $text);
		
		switch (strtolower($align)) {
			case 'right':
				$x -= $bounds[2];
				break;
			case 'center':
				$x -= ceil($bounds[2]/2);
				break;
		}
		
		imagettftext ($image, $size, 0, $x, $y, $color, $fontfile, $text);
		
		
		return $image;
	}

	static public function imagecreatefrombmp($read) {
		// Load the image into a string
		// $file = fopen($p_sFile,"rb");
		// $read = fread($file,10);
		// while(!feof($file)&&($read<>""))
		// $read .= fread($file,1024);
		$temp = unpack("H*", $read);
		$hex = $temp[1];
		$header = substr($hex, 0, 108);
		
		// Process the header
		// Structure: http://www.fastgraph.com/help/bmp_header_format.html
		if (substr($header, 0, 4) == "424d") {
			// Cut it in parts of 2 bytes
			$header_parts = str_split($header, 2);
			
			// Get the width 4 bytes
			$width = hexdec($header_parts[19] . $header_parts[18]);
			
			// Get the height 4 bytes
			$height = hexdec($header_parts[23] . $header_parts[22]);
			
			// Unset the header params
			unset($header_parts);
		}
		
		// Define starting X and Y
		$x = 0;
		$y = 1;
		
		// Create newimage
		$image = imagecreatetruecolor($width, $height);
		
		// Grab the body from the image
		$body = substr($hex, 108);
		
		// Calculate if padding at the end-line is needed
		// Divided by two to keep overview.
		// 1 byte = 2 HEX-chars
		$body_size = (strlen($body) / 2);
		$header_size = ($width * $height);
		
		// Use end-line padding? Only when needed
		$usePadding = ($body_size > ($header_size * 3) + 4);
		
		// Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption
		// Calculate the next DWORD-position in the body
		for ($i = 0; $i < $body_size; $i += 3) {
			// Calculate line-ending and padding
			if ($x >= $width) {
				// If padding needed, ignore image-padding
				// Shift i to the ending of the current 32-bit-block
				if ($usePadding)
					$i += $width % 4;
					
					// Reset horizontal position
				$x = 0;
				
				// Raise the height-position (bottom-up)
				$y ++;
				
				// Reached the image-height? Break the for-loop
				if ($y > $height)
					break;
			}
			
			// Calculation of the RGB-pixel (defined as BGR in image-data)
			// Define $i_pos as absolute position in the body
			$i_pos = $i * 2;
			$r = hexdec($body[$i_pos + 4] . $body[$i_pos + 5]);
			$g = hexdec($body[$i_pos + 2] . $body[$i_pos + 3]);
			$b = hexdec($body[$i_pos] . $body[$i_pos + 1]);
			
			// Calculate and draw the pixel
			$color = imagecolorallocate($image, $r, $g, $b);
			imagesetpixel($image, $x, $height - $y, $color);
			
			// Raise the horizontal position
			$x ++;
		}
		
		// Unset the body / free the memory
		unset($body);
		
		// Return image-object
		return $image;
	}
}